var DraggingElementView = joint.dia.ElementView.extend({

    initialize: function() {

        joint.dia.ElementView.prototype.initialize.apply(this, arguments);

        this.model.on('transition:end', function(el, path) {

            if (path == 'position' && this._speed > 5) {
                this._speed /= el.get('bounciness') || 2;
                this.fly({ angle: 180 - this._angle, speed: this._speed });
            }

        }, this);

        this.model.on('change:position', function(el, chng) {

            this._angle = g.point(el.get('position')).theta(el.previous('position'));
            //this._speed = we are using constant speed for simplicity

            // constraints

            if (chng.x < 0 || chng.x > (this.paper.options.width - el.get('size').width)) {

                this._angle -= 180;

                el.stopTransitions('position');
                el.get('position').x = el.previous('position').x;
            }

        }, this);

        // display 'drag me!'
        this._textId = this.model.transition('attrs/label/opacity', 1, {
            delay: (1 + Math.random()) * 3000,
            duration: 3000,
            timingFunction: joint.util.timing.inout,
            valueFunction: function(a, b) { return function(t) { return a + b * (1 - Math.abs(1 - 2 * t)); }; }
        });
    },

    fly: function(opt) {

        opt = opt || {};

        joint.util.defaults(opt, {
            speed: 100,
            angle: 90
        });

        var ga = this.paper.options.gravityAccelaration || 9.81;
        var h0 = this.paper.options.height - this.model.position().y - this.model.size().height;
        var v0 = opt.speed;
        var sin1 = Math.sin(g.toRad(opt.angle));

        var flightTime = (v0 * sin1 + Math.sqrt(Math.pow(v0, 2) * Math.pow(sin1, 2) + 2 * h0 * ga)) / ga;

        this.model.transition('position', opt, {
            duration: 100 * flightTime,
            valueFunction: function(position, params) {
                return function(t) {
                    t = flightTime * t;
                    return {
                        x: position.x + (params.speed * t * Math.cos(Math.PI / 180 * params.angle)),
                        y: position.y - params.speed * t * Math.sin(Math.PI / 180 * params.angle) + ga / 2 * t * t
                    };
                };
            }
        });

        this.model.transition('angle', -opt.angle, { duration: 100 * flightTime });

        this._speed = opt.speed;
        this._angle = opt.angle;
    },

    // interaction

    pointerdown: function(evt, x, y) {

        // do not allow drag element while it's still in a transition
        if (this.model.getTransitions().indexOf('position') > -1) return;

        // create a link
        this._link = new joint.shapes.standard.Link({
            source: this.model.getBBox().center(),
            target: new g.Point(x, y),
            z: -1,
            attrs: {
                line: {
                    stroke: 'rgba(0,0,0,0.1)',
                    strokeWidth: 6,
                    targetMarker: {
                        'stroke': 'black',
                        'stroke-width': 2,
                        'd': 'M 20 -10 L 0 0 L 20 10 z'
                    }
                }
            }
        });

        this.paper.model.addCell(this._link);

        // marker arrow color change
        this._link.on('change:target', function(lnk) {
            var dist = lnk.get('source').distance(lnk.get('target'));
            lnk.attr('line/targetMarker/fill', this.colorFunction(dist / this.maxDist / Math.sqrt(2)));
        }, {
            maxDist: Math.max(this.paper.options.width, this.paper.options.height),
            colorFunction: joint.util.interpolate.hexColor('#ffffff', '#ff0000')
        });


        // cancel displaying 'drag me!' if dragging already starts
        if (this._textId) {
            clearTimeout(this._textId);
            delete this._textId;
        }
    },

    pointermove: function(evt, x, y) {

        if (this._link) this._link.set('target', g.point(x, y));
    },

    pointerup: function(evt, x, y) {

        if (!this._link) return;

        this.fly({
            angle: Math.abs(this._link.get('target').theta(this._link.get('source')) - 180),
            speed: this._link.get('source').distance(this._link.get('target')) / 2
        });

        var cell = this.paper.model.getCell(this._link.id);

        if (cell) cell.remove();

        delete this._link;
    }
});

joint.dia.Element.define('demo.Ball', {
    angle: 0,
    attrs: {
        label: {
            text: 'Drag me!',
            fontSize: 40,
            fontWeight: 900,
            refX: .5,
            refY: -20,
            textVerticalAnchor: 'middle',
            textAnchor: 'middle',
            fill: 'white',
            strokeWidth: 2,
            stroke: 'black',
            opacity: 0,
            pointerEvents: 'none'
        },
        ball: {
            refWidth: 1,
            refHeight: 1
        }
    }
}, {
    markup: [{
        tagName: 'text',
        selector: 'label'
    }, {
        tagName: 'image',
        selector: 'ball'
    }]
});

// app

var graph = new joint.dia.Graph;

new joint.dia.Paper({
    el: document.getElementById('paper'),
    width: 650,
    height: 400,
    gridSize: 1,
    model: graph,
    gravityAccelaration: 9.81,
    elementView: DraggingElementView
});

var ball1 = new joint.shapes.demo.Ball({
    bounciness: 1.5,
    angle: 0,
    position: { x: 400, y: 350 },
    size: { width: 50, height: 50 },
    attrs: { image: { 'xlink:href': '' }}
});

var ball2 = new joint.shapes.demo.Ball({
    bounciness: 3,
    position: { x: 250, y: 370 },
    size: { width: 30, height: 30 },
    attrs: { image: { 'xlink:href': '' }}
});

graph.addCells([ball1, ball2]);
